package ace.cmp.spring.data.jdbc.querydsl.core.util.impl;

import ace.cmp.spring.data.jdbc.querydsl.core.annotations.Ignore;
import ace.cmp.spring.data.jdbc.querydsl.core.annotations.LogicDelete;
import ace.cmp.spring.data.jdbc.querydsl.core.util.QuerydslAnnotationUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.data.annotation.*;
import org.springframework.util.Assert;

/**
 * @author caspar
 * @date 2023/8/25 9:51
 */
public class QuerydslAnnotationUtilsImpl implements QuerydslAnnotationUtils {
  // key1- entityClass.getTypeName key2- AnnotationClass.getTypeName key3- FieldClass.getName
  private final Map<String, Map<String, Map<String, Field>>> querydslDataJdbcAnnFieldHolder =
      new HashMap<>();

  @Override
  public void setAllFrameworkAnnotationExceptIdFieldToNull(Object entity) {
    Assert.notNull(entity, "entity can not be null");
    Class entityClass = entity.getClass();
    this.getAllFieldByFrameworkAnnotation(entityClass)
        .forEach(field -> this.setFieldToNull(entity, field));
  }

  @Override
  public Set<Field> getAllFieldByFrameworkAnnotation(Class entityType) {
    return new HashSet<>(this.getAllFieldByFrameworkAnnotationMap(entityType).values());
  }

  @Override
  public Map<String, Field> getAllFieldByFrameworkAnnotationMap(Class entityType) {
    return Stream.of(
            CreatedDate.class,
            CreatedBy.class,
            LastModifiedDate.class,
            LastModifiedBy.class,
            Version.class,
            LogicDelete.class,
            Ignore.class)
        .map(annotationCls -> this.getFieldByAnnotationMap(entityType, annotationCls))
        .map(Map::entrySet)
        .flatMap(Set::stream)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  }

  private Map<String, Field> getFieldByAnnotationMap(
      Class entityType, final Class<? extends Annotation> annotationCls) {
    Map<String, Map<String, Field>> entityClassMap =
        querydslDataJdbcAnnFieldHolder.computeIfAbsent(
            entityType.getTypeName(), (k) -> new HashMap<>());
    Map<String, Field> nameAndFieldMap =
        entityClassMap.computeIfAbsent(
            annotationCls.getTypeName(),
            (k) ->
                FieldUtils.getFieldsListWithAnnotation(entityType, annotationCls).stream()
                    .collect(Collectors.toMap(Field::getName, f -> f)));

    return nameAndFieldMap;
  }

  private Set<Field> getFieldByAnnotation(
      Class entityType, final Class<? extends Annotation> annotationCls) {
    return new HashSet<>(this.getFieldByAnnotationMap(entityType, annotationCls).values());
  }

  private void setFieldToNull(Object entity, Field field) {
    try {
      PropertyUtils.setProperty(entity, field.getName(), null);
    } catch (Exception ex) {
    }
  }
}
